#include "../../jlib/cipher/crc.h"
#include "../../jlib/cipher/hex.h"
#include "../../jlib/cipher/b64.h"
#include "../../jlib/misc/steady_timer.h"
#include <jlib/jlib.h>
#include <vector>
#include <string>
#include <random>
#include <cstring>

USE_JLIB_NS

//#define MAX (64 * 1024 * 1024) // 64MB
#define MAX (64 * 1024) // 64KB
//#define MAX (32) // 64B


u64 crc (const void* src, uwl len, u64 poly, u64 init, u64 oxor, bool rev, u64 bits) noexcept {
  auto sp = (const u8*)src;
  auto bi = 0;
  u8 cache = 0;
  auto take = [&] () {
    auto i = bi++ & 7;
    if (i == 0)
      cache = !rev ? BitSet<u8>::swap<u8>(*sp++) : *sp++;
    else
      cache >>= 1;
    return (u64)(cache & 1);
  };
  u64 head = 0;
  u64 lpad = bits;
  u64 size = len << 3;
  u64 hbit = 1ULL << (bits - 1);
  for (auto i = 0; i < bits; ++i) {
    head <<= 1;
    if (i < size)
      head |= take();
    else
      lpad--;
  }
  for (init ^= head; bi < size; ) {
    if (init & hbit) {
      init <<= 1;
      init |= take();
      init ^= poly;
    }
    else {
      init <<= 1;
      init |= take();
    }
  }
  for (size += lpad; bi < size; ++bi) {
    if (init & hbit) {
      init <<= 1;
      init ^= poly;
    }
    else {
      init <<= 1;
    }
  }
  if (rev)
    init = BitSet<u64>::swap<u64>(init) >> (64 - bits);
  if (bits < 64)
    init &= (1UL << bits) - 1;
  return init ^ oxor;
}


uwl encodeHex (void* dst_buf, const void* src_buf, uwl len, bool lower_case = false) noexcept {
  auto tr = lower_case ? "0123456789abcdef" : "0123456789ABCDEF";
  auto src = (const u8*)src_buf;
  auto dst = (c8*)dst_buf;
  for (auto end = src + len; src < end; ++src) {
    *dst++ = tr[*src >> 4];
    *dst++ = tr[*src & 0xF];
  }
  return dst - (c8*)dst_buf;
}


uwl decodeHex (void* dst_buf, const void* src_buf, uwl len) noexcept {
  if (len & 1)
    return 0;
  auto tr =
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\x0A\x0B\x0C\x0D\x0E\x0F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\x0A\x0B\x0C\x0D\x0E\x0F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
  auto src = (const u8*)src_buf;
  auto dst = (u8*)dst_buf;
  for (auto end = src + len; src < end; ++dst) {
    auto h4 = (u8)tr[*src++];
    auto l4 = (u8)tr[*src++];
    *dst = (u8)((h4 << 4) | l4);
  }
  return dst - (u8*)dst_buf;
}


uwl encodeBase64 (void* dst_buf, const void* src_buf, uwl len, bool padding = true) noexcept {
  auto tr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  auto src = (const u8*)src_buf;
  auto dst = (c8*)dst_buf;
  auto tail = len % 3;
  len -= tail;
  for (auto end = src + len; src < end; src += 3, dst += 4) {
    auto i0 = src[0] >> 2;
    auto i1 = ((src[0] << 4) & 0x30) | (src[1] >> 4);
    auto i2 = ((src[1] << 2) & 0x3C) | (src[2] >> 6);
    auto i3 = src[2] & 0x3F;
    dst[0] = tr[i0];
    dst[1] = tr[i1];
    dst[2] = tr[i2];
    dst[3] = tr[i3];
  }
  if (tail == 2) {
    auto i0 = src[0] >> 2;
    auto i1 = ((src[0] << 4) & 0x30) | (src[1] >> 4);
    auto i2 = ((src[1] << 2) & 0x3C);
    *dst++ = tr[i0];
    *dst++ = tr[i1];
    *dst++ = tr[i2];
    if (padding)
      *dst++ = '=';
  }
  else if (tail == 1) {
    auto i0 = src[0] >> 2;
    auto i1 = (src[0] << 4) & 0x30;
    *dst++ = tr[i0];
    *dst++ = tr[i1];
    if (padding) {
      *dst++ = '=';
      *dst++ = '=';
    }
  }
  return dst - (c8*)dst_buf;
}


uwl decodeBase64 (void* dst_buf, const void* src_buf, uwl len) noexcept {
  auto tr =
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3E\xFF\xFF\xFF\x3F"
    "\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\xFF\xFF\xFF\x00\xFF\xFF"
    "\xFF\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E"
    "\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xFF\xFF\xFF\xFF\xFF"
    "\xFF\x1A\x1B\x1C\x1D\x1E\x1F\x20\x21\x22\x23\x24\x25\x26\x27\x28"
    "\x29\x2A\x2B\x2C\x2D\x2E\x2F\x30\x31\x32\x33\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
  if (!len)
    return 0;
  auto src = (const u8*)src_buf;
  auto dst = (u8*)dst_buf;
  auto tail = len & 3;
  if (tail) {
    if (tail == 1)
      return 0;
    len -= tail;
  }
  else if (src[len - 1] == '=') {
    tail = src[len - 2] == '=' ? 2 : 3;
    len -= 4;
  }
  for (auto end = src + len; src < end; src += 4, dst += 3) {
    auto s0 = tr[src[0]];
    auto s1 = tr[src[1]];
    auto s2 = tr[src[2]];
    auto s3 = tr[src[3]];
    dst[0] = (u8)((s0 << 2) | ((s1 >> 4) & 0x03));
    dst[1] = (u8)((s1 << 4) | ((s2 >> 2) & 0x0F));
    dst[2] = (u8)((s2 << 6) | s3);
  }
  if (tail) {
    auto s0 = tr[src[0]];
    auto s1 = tr[src[1]];
    *dst++ = (u8)((s0 << 2) | ((s1 >> 4) & (c8)0x03));
    if (tail == 3) {
      auto s2 = tr[src[2]];
      *dst++ = (u8)((s1 << 4) | ((s2 >> 2) & 0x0F));
    }
  }
  return dst - (u8*)dst_buf;
}



uwl genData (std::mt19937& gen, u8* buf) noexcept {
  auto length = std::uniform_int_distribution<uwl>(1, MAX)(gen);
  for (auto i = 0; i < length; ++i)
    buf[i] = std::uniform_int_distribution<u8>(48, 57)(gen);
  buf[length] = 0;
  return length;
}

template<typename Type, u8 Bits, Type Poly, Type Init, Type Xor, bool Rev>
void doCrc (const c8* name, const void* src, uwl len) {
  auto res = (u64)CRC<Type, Poly, Init, Xor, Rev, Bits>::checksum(src, len);
  auto check = crc(src, len, Poly, Init, Xor, Rev, Bits);
  //log_d("%-16s %16u %16lX %16lX %16lX %16s %16lX %16lX", name, Bits, (u64)Poly, (u64)Init, (u64)Xor, Rev ? "Yes" : "No", res, check);
  abort_assert(res == check, "CRC checksum");
}

u8 data[MAX];
u8 buf0[MAX];
u8 buf1[MAX];
c8 buf2[MAX * 2 + 32];
c8 buf3[MAX * 2 + 32];


int main (int argc, char** argv) {

  std::random_device device;
  std::mt19937 random(device());
  SteadyTimer timer;

  for (auto round = 1; round <= 4; ++round) {
    uwl len0, len1, size = genData(random, data);
    log_i("##########  Round %d with %zd bytes  #########", round, size);
    //log_d("%s", data);

    log_i("%-16s %16s %16s %16s %16s %16s %16s %16s", "Name", "Bit Width", "Polynomial", "Initial Value", "Final Xor", "Reverse I/O", "Result", "Check");
    doCrc<u8, 3, 0x3, 0x7, 0x0, true>("CRC-3/ROHC", data, size);
    doCrc<u8, 4, 0x3, 0x0, 0x0, true>("CRC-4/ITU", data, size);
    doCrc<u8, 5, 0x05, 0x1F, 0x00, true>("CRC-5/USB", data, size);
    doCrc<u8, 5, 0x09, 0x09, 0x00, false>("CRC-5/EPC", data, size);
    doCrc<u8, 5, 0x15, 0x00, 0x00, true>("CRC-5/ITU", data, size);
    doCrc<u8, 6, 0x03, 0x00, 0x00, true>("CRC-6/ITU", data, size);
    doCrc<u8, 6, 0x19, 0x00, 0x00, true>("CRC-6/DARC", data, size);
    doCrc<u8, 7, 0x09, 0x00, 0x00, false>("CRC-7", data, size);
    doCrc<u8, 7, 0x4F, 0x7F, 0x00, true>("CRC-7/ROHC", data, size);
    doCrc<u8, 8, 0x07, 0x00, 0x00, false>("CRC-8", data, size);
    doCrc<u8, 8, 0x07, 0x00, 0x00, false>("CRC-8/ITU", data, size);
    doCrc<u8, 8, 0x07, 0xFF, 0x00, true>("CRC-8/ROHC", data, size);
    doCrc<u8, 8, 0x1D, 0xFD, 0x00, false>("CRC-8/I-CODE", data, size);
    doCrc<u8, 8, 0x31, 0x00, 0x00, true>("CRC-8/MAXIM", data, size);
    doCrc<u8, 8, 0x39, 0x00, 0x00, true>("CRC-8/DARC", data, size);
    doCrc<u8, 8, 0x9B, 0x00, 0x00, true>("CRC-8/WCDMA", data, size);
    doCrc<u16, 10, 0x233, 0x000, 0x000, false>("CRC-10", data, size);
    doCrc<u16, 11, 0x385, 0x01A, 0x000, false>("CRC-11", data, size);
    doCrc<u16, 12, 0x80F, 0x000, 0x000, false>("CRC-12/3GPP", data, size);
    doCrc<u16, 12, 0x80F, 0x000, 0x000, false>("CRC-12/DECT", data, size);
    doCrc<u16, 14, 0x0805, 0x0000, 0x0000, true>("CRC-14/DARC", data, size);
    doCrc<u16, 15, 0x4599, 0x0000, 0x0000, false>("CRC-15", data, size);
    doCrc<u16, 16, 0x0589, 0x0000, 0x0000, false>("CRC-16/DECT-R", data, size);
    doCrc<u16, 16, 0x0589, 0x0000, 0x0000, false>("CRC-16/DECT-X", data, size);
    doCrc<u16, 16, 0x1021, 0x0000, 0x0000, false>("CRC-16/ACOR", data, size);
    doCrc<u16, 16, 0x1021, 0x1D0F, 0x0000, false>("CRC-16/ACOR", data, size);
    doCrc<u16, 16, 0x1021, 0xFFFF, 0x0000, false>("CRC-16/ACOR", data, size);
    doCrc<u16, 16, 0x1021, 0xFFFF, 0x0000, false>("CRC-16/ACOR", data, size);
    doCrc<u16, 16, 0x1021, 0x0000, 0x0000, true>("CRC-16/CCIT", data, size);
    doCrc<u16, 16, 0x1021, 0x89EC, 0x0000, true>("CRC-16/TMS37157", data, size);
    doCrc<u16, 16, 0x1021, 0xB2AA, 0x0000, true>("CRC-16/RIELLO", data, size);
    doCrc<u16, 16, 0x1021, 0xC6C6, 0x0000, true>("CRC-A", data, size);
    doCrc<u16, 16, 0x1021, 0xFFFF, 0x0000, true>("CRC-16/MCRF4XX", data, size);
    doCrc<u16, 16, 0x1021, 0xFFFF, 0x0000, true>("CRC-16/IBM-SDLC", data, size);
    doCrc<u16, 16, 0x3D65, 0x0000, 0x0000, false>("CRC-16/EN-13757", data, size);
    doCrc<u16, 16, 0x3D65, 0x0000, 0x0000, true>("CRC-16/DNP", data, size);
    doCrc<u16, 16, 0x8005, 0x0000, 0x0000, false>("CRC-16/BUYPASS", data, size);
    doCrc<u16, 16, 0x8005, 0x800D, 0x0000, false>("CRC-16/DDS-110", data, size);
    doCrc<u16, 16, 0x8005, 0x0000, 0x0000, true>("CRC-16/ARC", data, size);
    doCrc<u16, 16, 0x8005, 0x0000, 0x0000, true>("CRC-16/MAXIM", data, size);
    doCrc<u16, 16, 0x8005, 0xFFFF, 0x0000, true>("CRC-16/USB", data, size);
    doCrc<u16, 16, 0x8005, 0xFFFF, 0x0000, true>("CRC-16/MODBUS", data, size);
    doCrc<u16, 16, 0x8BB7, 0x0000, 0x0000, false>("CRC-16/T10-DIF", data, size);
    doCrc<u16, 16, 0xA097, 0x0000, 0x0000, false>("CRC-16/TELEDISK", data, size);
    doCrc<u32, 24, 0x5D6DCB, 0xABCDEF, 0x0000, false>("CRC-24/FLEXRAY-B", data, size);
    doCrc<u32, 24, 0x5D6DCB, 0xFEDCBA, 0x0000, false>("CRC-24/FLEXRAY-A", data, size);
    doCrc<u32, 24, 0x864CFB, 0xB704CE, 0x0000, false>("CRC-24/OPENPGP", data, size);
    doCrc<u32, 32, 0x000000AF, 0x00000000, 0x00000000, false>("CRC-32/XFER", data, size);
    doCrc<u32, 32, 0x04C11DB7, 0x00000000, 0x00000000, false>("CRC-32/POSIX", data, size);
    doCrc<u32, 32, 0x04C11DB7, 0xFFFFFFFF, 0x00000000, false>("CRC-32/BZIP2", data, size);
    doCrc<u32, 32, 0x04C11DB7, 0xFFFFFFFF, 0x00000000, false>("CRC-32/MPEG-2", data, size);
    doCrc<u32, 32, 0x04C11DB7, 0xFFFFFFFF, 0x00000000, true>("CRC-32/PKZIP", data, size);
    doCrc<u32, 32, 0x04C11DB7, 0xFFFFFFFF, 0x00000000, true>("CRC-32/JAM", data, size);
    doCrc<u32, 32, 0x1EDC6F41, 0xFFFFFFFF, 0x00000000, true>("CRC-32C", data, size);
    doCrc<u32, 32, 0x814141AB, 0x00000000, 0x00000000, false>("CRC-32Q", data, size);
    doCrc<u32, 32, 0xA833982B, 0xFFFFFFFF, 0x00000000, true>("CRC-32D", data, size);
    doCrc<u64, 40, 0x0004820009, 0x0000000000, 0x0000000000000000, false>("CRC-40/GSM", data, size);
    doCrc<u64, 64, 0x42F0E1EBA9EA3693, 0x0000000000000000, 0x0000000000000000, false>("CRC-64/ECMA", data, size);
    doCrc<u64, 64, 0x000000000000001B, 0x0000000000000000, 0x0000000000000000, false>("CRC-64/ISO", data, size);

    timer.restart();
    len0 = HEX::encode(buf2, data, size);
    log_d("Hex Encode (ASM) in %011.9lf seconds", timer.duration());
    buf2[len0] = 0;
    // log_d("%s", buf2);
    timer.restart();
    len1 = encodeHex(buf3, data, size);
    log_d("Hex Encode (CXX) in %011.9lf seconds", timer.duration());
    buf3[len1] = 0;
    // log_d("%s", buf3);
    abort_assert(len0 == len1, "Hex Encode");
    abort_assert(memcmp(buf2, buf3, len0) == 0, "Hex Encode");

    timer.restart();
    len0 = HEX::decode(buf0, buf2, len0);
    log_d("Hex Decode (ASM) in %011.9lf seconds", timer.duration());
    buf0[len0] = 0;
    // log_d("%s", buf0);
    timer.restart();
    len1 = decodeHex(buf1, buf3, len1);
    log_d("Hex Decode (CXX) in %011.9lf seconds", timer.duration());
    buf1[len1] = 0;
    // log_d("%s", buf1);
    abort_assert(len0 == len1, "Hex Decode");
    abort_assert(memcmp(buf0, buf1, len0) == 0, "Hex Decode");
    abort_assert(memcmp(data, buf0, size) == 0, "Hex Decode");

    timer.restart();
    len0 = B64::encode(buf2, data, size);
    log_d("Base64 Encode (ASM) in %011.9lf seconds", timer.duration());
    buf2[len0] = 0;
    // log_d("%s", buf2);
    timer.restart();
    len1 = encodeBase64(buf3, data, size);
    log_d("Base64 Encode (CXX) in %011.9lf seconds", timer.duration());
    buf3[len1] = 0;
    // log_d("%s", buf3);
    abort_assert(len0 == len1, "Base64 Encode");
    abort_assert(memcmp(buf2, buf3, len0) == 0, "Base64 Encode");

    timer.restart();
    len0 = B64::decode(buf0, buf2, len0);
    log_d("Base64 Decode (ASM) in %011.9lf seconds", timer.duration());
    buf0[len0] = 0;
    // log_d("%s", buf0);
    timer.restart();
    len1 = decodeBase64(buf1, buf3, len1);
    log_d("Base64 Decode (CXX) in %011.9lf seconds", timer.duration());
    buf1[len1] = 0;
    // log_d("%s", buf1);
    abort_assert(len0 == len1, "Base64 Decode");
    abort_assert(memcmp(buf0, buf1, len0) == 0, "Base64 Decode");
    abort_assert(memcmp(data, buf0, size) == 0, "Base64 Decode");
  }

  return 0;
}
